<?php

class TestSet {
  var $name;
  var $running;
  var $methods;
  var $filename;

  function __construct($name="") {
    $this->name = $name;
    $this->methods = array();
  }

  function load($filename) {
    require_once($filename);
    ereg("(^|.*/)([^/]*).php$", $filename, $matches);
    $name = $matches[2];
    $set = new $name($name);
    $set->code = file($filename);
    return $set;
  }



  function add_assertion($assertion) {
    $this->pass_count += $assertion->passed ? 1 : 0;
    $this->assertion_count++;
    if ($this->methods[$this->running]) {
      $this->methods[$this->running]->add_assertion($assertion);
    }
  }



  function run_tests($print=false) {
    $methods = get_class_methods($this->name);
    if (!is_array($methods)) return;

    foreach($methods as $method) {
      if (ereg("^test_", $method)) {
        $this->methods[$method] = new TestMethod($method, $this->name);
        $this->running = $method;
        ob_start();
        $this->$method();
        $this->methods[$method]->output = ob_get_contents();
        ob_end_clean();

        for($i=0; $i<count($this->code); $i++) {
          if (ereg("function $method", $this->code[$i])) {
            $this->methods[$method]->line = $i+1;
          }
        }
      }
    }
  }


  function print_all() {
    echo clean_html_code($this->toHtml());
  }


  function resultsTableToHtml() {
    if ($this->pass_count == $this->assertion_count) {
      $results_class = "Hidden";
    }

    $html .= "<div id=\"$this->name.details\" "
             . "class=\"ResultsTable $results_class\">";

                $odd = true;
                foreach($this->methods as $method) {
                  $html .= $method->toHtml($odd);
                  $odd = $odd ? false : true;
                }

    $html .= "</div>";
    return $html;
  }

  function toHtml() {
    if ($this->pass_count == $this->assertion_count) {
      $summary_class = "Pass";
    } else {
      $summary_class = "Fail";
    }

    $failures = $this->assertion_count - $this->pass_count;
    $html = "
      <div class=\"TestSet\">
        <div class=\"Head\">
          <h2>$this->name </h2>
          <div class=\"Summary $summary_class\">
            $this->assertion_count assertions, $failures failures.
            <a href=\"javascript:toggle_div('$this->name.details');\">Show details</a>.
          </div>
        </div>
    ";


    $html .= $this->resultsTableToHtml();

    $html .= "
      </div>
    ";

    return $html;
  }



  ###############
  ## MATCHERS
  #



  # Basic truth

  function assert($assertion, $user_message="", $expectation=true) {
    echo s($assertion) . "=" . s($expection) . "? ";
    $passed = ($assertion == $expectation);
    if ($assertion) {
      $message = "Assertion is true";
    } else {
      $message = "Assertion is NOT true";
    }
    $assert = new TestAssertion($passed, $message, $user_message);
    $this->add_assertion($assert);
    return $assert;
  }

  function assert_false($assertion, $user_message="") {
    return $this->assert($assertion, $user_message, false);
  }




  # Array matching
  function array_to_simple_string($arr, $error_pos=-1) {
    echo "position is $error_pos";
    if (!is_array($arr)) return $arr;
    $str = "[";
    $pos = 0;
    foreach($arr as $item) {
      if ($str != "[") $str .= ",";
      if ($pos == $error_pos) $str .= "<b>";
      $str .= "'" . htmlentities($item) . "'";
      if ($pos == $error_pos) $str .= "</b>";
      $pos++;
    }
    $str .= "]";
    return $str;
  }

  function assert_array_match($a, $b) {
    $passed = true;
    $message = "";
    $count = min(count($a), count($b));
    for($i=0; $i<$count; $i++) {
      if ($a[$i] != $b[$i]) {
        $passed = false;
        $position = $i;
        continue;
      }
    }

    if ( (count($a) > count($b)) || (count($b) > count($a)) ) {
      $passed = false;
      $position = min(count($a), count($b));
    }

    if ($passed) {
      $message = "array matches <span class=\"Expected\">"
        . TestSet::array_to_simple_string($a) . "</span>";
    } else {
      $message = "<span class=\"Expected\">"
        . TestSet::array_to_simple_string($a, $position) . "</span> does not match "
        . "<span class=\"Actual\">" 
        . TestSet::array_to_simple_string($b, $position) 
        . "</span>";
    }
    $assert = new TestAssertion($passed, $message);
    $this->add_assertion($assert);
    return $assert;
  }




  # HTML Validation

  function html_to_tag_list($html) {
    $arr = preg_split("/(^|>)[^<]*(<|$)/", $html);
    for($i=0; $i<count($arr)+1; $i++) {
      if (strlen($arr[$i]) < 1) unset($arr[$i]);
    }
    return array_values($arr);
  }


  function highlight_tag($position, $html) {
    $regex = "^([^<]*";
    for($i=0;$i<$position;$i++) {
        $regex .= "<[^>]*>[^<]*";
    }
    $regex .= ")(<[^>]*>)(.*)$";
    
    echo "looking for $regex in " . $html;
    ereg($regex, $html, $matches);
    print_r($matches);

    $source = htmlentities($matches[1]) . "<b>" 
      . htmlentities($matches[2]) . "</b>"
      . htmlentities($matches[3]);
    return $source;
  }


  function assert_valid_html($html, $user_message="") {
    $valid = true;

    $tags = $this->html_to_tag_list($html);
    $stack = array();
    $singles = array("br", "img", "link");

    foreach($tags as $position=>$tag) {
      ereg("^(/?)([a-zA-Z]+)", $tag, $matches);
      $close = ($matches[1] == "/");
      $base = $matches[2];
      
      print_r($matches);

      $single = false;
      foreach($singles as $match) {
        if ($base == $match) $single = true;
      }

      if (!$single && !$close) {
        echo "pushing $base. ";
        array_push($stack, $base);
      } else if (!$single && $close) {
        $top = array_pop($stack);
        echo "popping $top ";
        if ($top === NULL) {
          $source = $this->highlight_tag($position, $html);
          $message = "has a closing &lt;$base&gt; tag (#" 
            . ($position+1) . ") without a corresponding opener in "
            . "<span class=\"Actual\">" . $source 
            . "</span>";
          $valid = false;
          continue;
        } else if ($top != $base) {

          $valid = false;
          continue;
        }
        echo "ok. ";
      }
    }

    if ($valid) {
      $message = "<span class=\"Actual\">"
        . htmlentities($html) . "</span> is valid HTML";
    }
    $assert = new TestAssertion($valid, $message, $user_message);
    $this->add_assertion($assert);
    return $assert;
  }



  # Regex matching

  function assert_match($pattern, $str, $expectation=true) {
    $matched = eregi($pattern, $str);
    if ($matched) {
      $message = "found <span class=\"Expected\">"
        . htmlentities($pattern) . "</span> in <span class=\"Actual\">"
        . htmlentities($str) . "</span>";
    } else {
      $message = "did not find <span class=\"Expected\">"
        . htmlentities($pattern) . "</span> in <span class=\"Actual\">"
        . htmlentities($str) . "</span>";
    }
    $passed = ($matched == $expectation);
    $assert = new TestAssertion($passed, $message);
    $this->add_assertion($assert);
    return $assert;
  }


  function assert_does_not_match($pattern, $str) {
    return $this->assert_match($pattern, $str, false);
  }




  # Equality

  function assert_equal($a, $b, $user_message="") {
    $passed = ($a == $b);
    $middle = $passed ? "equals" : "does not equal";
    $message = "<span class=\"Actual\">"
      . htmlentities($a) . "</span> $middle <span class=\"Expected\">"
      . htmlentities($b) . "</span>";

    $assert = new TestAssertion($passed, $message, $user_message);
    $this->add_assertion($assert);
    return $assert;
  }



  #
  ## end matchers
  ###################

}

?>
